/**
 * @author: martin
 * @since:2018.07.15
 * @version: v_1.0
 * @description: HD-Wallet API
 */

var ethers = require('ethers');

var Wallet = ethers.Wallet;
var providers = ethers.providers;

let fromPrivateKeyWallet = (privateKey) => {
    // Private Key
    var privateKey = "0x0123456789012345678901234567890123456789012345678901234567890123";
    var wallet = new Wallet(privateKey);

    console.log("fromPrivateKeyWallet Address: " + wallet.address);
    // "Address: 0x14791697260E4c9A71f18484C9f997B308e59325"
}
let fromRandomWallet = () => {
    // Random Wallet
    var wallet = Wallet.createRandom();
    console.log("fromRandomWallet Address: " + wallet.address);
    // "Address: ... this will be different every time ..."
}
let fromEncryptedWallet = () => {
    // Secret Storage Wallet (e.g. Geth or Parity)
    var data = {
        id: "fb1280c0-d646-4e40-9550-7026b1be504a",
        address: "88a5c2d9919e46f883eb62f7b8dd9d0cc45bc290",
        Crypto: {
            kdfparams: {
                dklen: 32,
                p: 1,
                salt: "bbfa53547e3e3bfcc9786a2cbef8504a5031d82734ecef02153e29daeed658fd",
                r: 8,
                n: 262144
            },
            kdf: "scrypt",
            ciphertext: "10adcc8bcaf49474c6710460e0dc974331f71ee4c7baa7314b4a23d25fd6c406",
            mac: "1cf53b5ae8d75f8c037b453e7c3c61b010225d916768a6b145adf5cf9cb3a703",
            cipher: "aes-128-ctr",
            cipherparams: {
                iv: "1dcdf13e49cea706994ed38804f6d171"
            }
        },
        "version": 3
    };

    var json = JSON.stringify(data);
    var password = "foo";
    Wallet.fromEncryptedWallet(json, password).then(function (wallet) {
        console.log("fromEncryptedWallet Address: " + wallet.address);
        // "Address: 0x88a5C2d9919e46F883EB62F7b8Dd9d0CC45bc290"
    });
}
let fromMnemonicWallet = () => {
    // Mnemonic Phrase
    var mnemonic = "radar blur cabbage chef fix engine embark joy scheme fiction master release";
    var wallet = Wallet.fromMnemonic(mnemonic);

    console.log("fromMnemonicWallet Address: " + wallet.address);
    // "Address: 0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9"
}
let fromBrainWallet = () => {
    // Brain Wallet
    var username = "support@ethers.io";
    var password = "password123";
    Wallet.fromBrainWallet(username, password).then(function (wallet) {
        console.log("fromBrainWallet Address: " + wallet.address);
        // "Address: 0x7Ee9AE2a2eAF3F0df8D323d555479be562ac4905"
    });
}

let queryNetwork = () => {
    // Query the Network
    var privateKey = '0x0123456789012345678901234567890123456789012345678901234567890123';
    var wallet = new ethers.Wallet(privateKey);
    wallet.provider = ethers.providers.getDefaultProvider();

    var balancePromise = wallet.getBalance();

    balancePromise.then(function (balance) {
        console.log('getBalance:', balance);
    });

    var transactionCountPromise = wallet.getTransactionCount();

    transactionCountPromise.then(function (transactionCount) {
        console.log('getTransactionCount:', transactionCount);
    });
}
let transfer = () => {
    // Transfer Ether
    var privateKey = '0x0123456789012345678901234567890123456789012345678901234567890123';
    var wallet = new ethers.Wallet(privateKey);
    wallet.provider = ethers.providers.getDefaultProvider();

    // We must pass in the amount as wei (1 ether = 1e18 wei), so we use
    // this convenience function to convert ether to wei.
    var amount = ethers.utils.parseEther('1.0');

    var address = '0x88a5c2d9919e46f883eb62f7b8dd9d0cc45bc290';
    var sendPromise = wallet.send(address, amount);

    sendPromise.then(function (transactionHash) {
        console.log('Transfer transactionHash:', transactionHash);
    });

    // These will query the network for appropriate values
    var options = {
        //gasLimit: 21000
        //gasPrice: utils.bigNumberify("20000000000")
    };

    var promiseSend = wallet.send(address, amount, options);

    promiseSend.then(function (transaction) {
        console.log('transaction', transaction);
    });
}
let sendingComplex = () => {
    // Sending (Complex) Transactions
    var privateKey = '0x0123456789012345678901234567890123456789012345678901234567890123';
    var wallet = new ethers.Wallet(privateKey);
    wallet.provider = ethers.providers.getDefaultProvider('ropsten');

    var transaction = {
        // Recommendation: omit nonce; the provider will query the network
        // nonce: 0,

        // Gas Limit; 21000 will send ether to another use, but to execute contracts
        // larger limits are required. The provider.estimateGas can be used for this.
        gasLimit: 1000000,

        // Recommendations: omit gasPrice; the provider will query the network
        //gasPrice: utils.bigNumberify("20000000000"),

        // Required; unless deploying a contract (in which case omit)
        to: "0x88a5C2d9919e46F883EB62F7b8Dd9d0CC45bc290",

        // Optional
        data: "0x",

        // Optional
        value: ethers.utils.parseEther("1.0"),

        // Recommendation: omit chainId; the provider will populate this
        // chaindId: providers.networks.homestead.chainId
    };

    // Estimate the gas cost for the transaction
    //var estimateGasPromise = wallet.estimateGas(transaction);

    //estimateGasPromise.then(function(gasEstimate) {
    //    console.log(gasEstimate);
    //});

    // Send the transaction
    var sendTransactionPromise = wallet.sendTransaction(transaction);

    sendTransactionPromise.then(function (transactionHash) {
        console.log(transactionHash);
    });
}

let parsingTransactions = () => {
    // Parsing Transactions.
    // Parses a raw hexStringOrArrayish into a Transaction.

    // Mainnet:
    var ethers = require('ethers');
    var Wallet = ethers.Wallet;
    var utils = ethers.utils;
    var privateKey = '0x0123456789012345678901234567890123456789012345678901234567890123';
    var wallet = new ethers.Wallet(privateKey);

    var raw = "0xf87083154262850500cf6e0083015f9094c149be1bcdfa69a94384b46a1f913" +
        "50e5f81c1ab880de6c75de74c236c8025a05b13ef45ce3faf69d1f40f9d15b007" +
        "0cc9e2c92f"

    var transaction = {
        nonce: 1393250,
        gasLimit: 21000,
        gasPrice: utils.bigNumberify("20000000000"),

        to: "0xc149Be1bcDFa69a94384b46A1F91350E5f81c1AB",

        value: utils.parseEther("1.0"),
        data: "0x",

        // This ensures the transaction cannot be replayed on different networks
        chainId: ethers.providers.networks.homestead.chainId
    };

    var signedTransaction = wallet.sign(transaction);
    var transaction = Wallet.parseTransaction(signedTransaction);

    console.log(transaction);
    // { nonce: 1393250,
    //   gasPrice: BigNumber { _bn: <BN: 4a817c800> },
    //   gasLimit: BigNumber { _bn: <BN: 5208> },
    //   to: '0xc149Be1bcDFa69a94384b46A1F91350E5f81c1AB',
    //   value: BigNumber { _bn: <BN: de0b6b3a7640000> },
    //   data: '0x',
    //   v: 38,
    //   r: '0x3cf1f5af8bd11963193451096d86635aed589572c184ac8696dd99c9c044ded3',
    //   s: '0x08c52dbf1383492c72598511bb135179ec93b062032d2a0d002214644ba39a2c',
    //   chainId: 1,
    //   from: '0x14791697260E4c9A71f18484C9f997B308e59325' }
}

let verifyingMessages = () => {
    // Verifying Messages
    // Returns the address that signed message with signature.

    var signature = "0xddd0a7290af9526056b4e35a077b9a11b513aa0028ec6c9880948544508f3c63" +
        "265e99e47ad31bb2cab9646c504576b3abc6939a1710afc08cbf3034d73214b8" +
        "1c";
    var address = Wallet.verifyMessage('hello world', signature);
    console.log(address);
    // '0x14791697260E4c9A71f18484C9f997B308e59325'
}

let connectProviders = () => {
    // var providers = require('ethers').providers;

    // Connect to Ropsten (the test network)

    // You may specify any of:
    // - boolean; true = ropsten, false = homestead
    // - object; { name: 'ropsten', chainId: 3 } (see ethers.networks);
    // - string; e.g. 'homestead', 'ropsten', 'rinkeby', 'kovan'
    var network = providers.networks.ropsten;

    // Connect to INFUA
    var infuraProvider = new providers.InfuraProvider(network);

    // Connect to Etherscan
    var etherscanProvider = new providers.EtherscanProvider(network);

    // Creating a provider to automatically fallback onto Etherscan
    // if INFURA is down
    var fallbackProvider = new providers.FallbackProvider([
        infuraProvider,
        etherscanProvider
    ]);

    // This is equivalent to using the getDefaultProvider
    var provider = providers.getDefaultProvider(network)

    // Connect to a local Parity instance
    var provider = new providers.JsonRpcProvider('http://localhost:8545', network);

    // Connect to an injected Web3's provider (e.g. MetaMask)
    var web3Provider = new providers.Web3Provider(web3.currentProvider, network);
}

let accountActions = () => {
    // Account Actions
    // var ethers = require('ethers');
    // var providers = ethers.providers;

    var provider = providers.getDefaultProvider('ropsten');

    var address = "0x02F024e0882B310c6734703AB9066EdD3a10C6e0";

    provider.getBalance(address).then(function (balance) {

        // balance is a BigNumber (in wei); format is as a sting (in ether)
        var etherString = ethers.utils.formatEther(balance);

        console.log("Balance: " + etherString);
    });

    provider.getTransactionCount(address).then(function (transactionCount) {
        console.log("Total Transactions Ever Send: " + transactionCount);
    });

    provider.resolveName("test.ricmoose.eth").then(function (address) {
        console.log("Address: " + address);
    });
}

let blockchainStatus = () => {
    // Current State

    var provider = providers.getDefaultProvider();

    provider.getBlockNumber().then(function (blockNumber) {
        console.log("Current block number: " + blockNumber);
    });

    provider.getGasPrice().then(function (gasPrice) {
        // gasPrice is a BigNumber; convert it to a decimal string
        gasPriceString = gasPrice.toString();

        console.log("Current gas price: " + gasPriceString);
    });
    // Blocks

    var provider = providers.getDefaultProvider();

    // Block Number
    provider.getBlock(3346773).then(function (block) {
        console.log(block);
    });

    // Block Hash
    var blockHash = "0x7a1d0b010393c8d850200d0ec1e27c0c8a295366247b1bd6124d496cf59182ad";
    provider.getBlock(blockHash).then(function (block) {
        console.log(block);
    });
    // Transactions

    var provider = providers.getDefaultProvider();

    var transactionHash = "0x7baea23e7d77bff455d94f0c81916f938c398252fb62fce2cdb43643134ce4ed";

    provider.getTransaction(transactionHash).then(function (transaction) {
        console.log(transaction);
    });

    provider.getTransactionReceipt(transactionHash).then(function (transactionReceipt) {
        console.log(transactionReceipt);
    });
}

let ethereumNameResolution = () => {
    // Resolving Names

    var providers = require('ethers').providers;
    var provider = providers.getDefaultProvider();
    provider.resolveName('registrar.firefly.eth').then(function (address) {
        console.log(address);
        // '0x6fC21092DA55B392b045eD78F4732bff3C580e2c'
    });
    // Looking up Addresses

    provider.lookupAddress('0x6fC21092DA55B392b045eD78F4732bff3C580e2c').then(function (name) {
        console.log(name);
        // 'registrar.firefly.eth'
    });
}

let eventTypes = () => {
    // Get notified on every new block
    provider.on('block', function (blockNumber) {
        console.log('New Block: ' + blockNumber);
    });

    // Get notified on account balance change
    provider.on('0x46Fa84b9355dB0708b6A57cd6ac222950478Be1d', function (balance) {
        console.log('New Balance: ' + balance);
    });

    // Get notified when a transaction is mined
    provider.once(transactionHash, function (transaction) {
        console.log('Transaction Minded: ' + transaction.hash);
        console.log(transaction);
    });

    // OR equivalently the waitForTransaction() returns a Promise

    provider.waitForTransaction(transactionHash).then(function (transaction) {
        console.log('Transaction Mined: ' + transaction.hash);
        console.log(transaction);
    });


    // Get notified when a contract event is logged
    var eventTopic = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
    provider.on([eventTopic], function (log) {
        console.log('Event Log');
        console.log(log);
    });
}

let etherscan = () => {
    // Etherscan
    // prototype.getEtherPrice()
    // Returns a Promise with the price of ether in USD.
    // prototype.getHistory(addressOrName[ , startBlock[ , endBlock]])
    // Returns a Promise with an array of Transaction Responses for each transaction to or from addressOrName between startBlock and endBlock(inclusive).

    var provider = new ethers.providers.EtherscanProvider();

    // Getting the current Ethereum price
    provider.getEtherPrice().then(function (price) {
        console.log("Ether price in USD: " + price);
    });


    // Getting the transaction history of an address
    var address = '0xb2682160c482eB985EC9F3e364eEc0a904C44C23';
    var startBlock = 3135808;
    var endBlock = 5091477;
    provider.getHistory(address, startBlock, endBlock).then(function (history) {
        console.log(history);
        // [
        //   {
        //     hash: '0x327632ccb6d7bb47b455383e936b2f14e6dc50dbefdc214870b446603b468675',
        //     blockHash: '0x0415f0d2741de45fb748166c7dc2aad9b3ff66bcf7d0a127f42a71d3e286c36d',
        //     blockNumber: 3135808,
        //     transactionIndex: 1,
        //     from: '0xb2682160c482eB985EC9F3e364eEc0a904C44C23',
        //     gasPrice: ethers.utils.bigNumberify('0x4a817c800'),
        //     gasLimit: ethers.utils.bigNumberify('0x493e0'),
        //     to: '0xAe572713CfE65cd7033774170F029B7219Ee7f70',
        //     value: ethers.utils.bigNumberify('0xd2f13f7789f0000'),
        //     nonce: 25,
        //     data: '0x',
        //     creates: null,
        //     networkId: 0
        //   },
        //   {
        //     hash: '0x7c10f2e7125a1fa5e37b54f5fac5465e8d594f89ff97916806ca56a5744812d9',
        //     ...
        //   }
        // ]
    });
}

let HDNode = () => {
    // HDNode
    // A Hierarchical Deterministic Wallet represents a large tree of private keys which can reliably be reproduced from an initial seed. Each node in the tree is represended by an HDNode which can be descended into.

    // A mnemonic phrase represents a simple way to generate the initial seed.

    // See the BIP 32 Specification to learn more about HD Wallets and hardened vs non-hardened nodes.

    // See the BIP 39 Specification to learn more about Mnemonic Phrases.

    // Creating Instances
    // ethers . HDNode . fromMnemonic ( mnemonic )
    // Create an HDNode from a mnemonic phrase.
    // ethers . HDNode . fromSeed ( seed )
    // Create an HDNode from a seed.
    // Prototype
    // prototype . privateKey
    // The hex string private key for this node.
    // prototype . publicKey
    // The (compressed) public key for this node.
    // prototype . chainCode
    // The chain code for this node.
    // prototype . index
    // The index (from the parent) of this node (0 for the master node).
    // prototype . depth
    // The depth within th hierarchy of this node.
    // prototype . derivePath ( path )
    // Derive the path from this node. Path is slash (/) delimited path components. The first component may be “m” for master (which enforces the starting node is infact a master node) and each subsequent path component should be a positive integer (up to 31 bits), which can optionally include an apostrophe (‘) to indicate hardened derivation for that path components. See below for some examples.
    // Static Methods
    // ethers . HDNode . mnemonicToEntropy ( mnemonic )
    // Convert a mnemonic to its binary entropy. (throws an error if the checksum is invalid)
    // ethers . HDNode . entropyToMnemonic ( entropy )
    // Convert the binary entropy to the mnemonic phrase.
    // ethers . HDNode . mnemonicToSeed ( mnemonic )
    // Compute the BIP39 seed from mnemonic.
    // ethers . HDNode . isValidMnemonic ( string )
    // Returns true if and only if the string is a valid mnemonic (including the checksum)
    // Examples
    var HDNode = require('ethers').HDNode;

    var mnemonic = "radar blur cabbage chef fix engine embark joy scheme fiction master release";

    var masterNode = HDNode.fromMnemonic(mnemonic);

    var standardEthereum = masterNode.derivePath("m/44'/60'/0'/0/0");
}
let promisesAll = () => {
    var ethers = require('ethers');
    var targetAddress = "0x02F024e0882B310c6734703AB9066EdD3a10C6e0";

    var privateKey = "0x0123456789012345678901234567890123456789012345678901234567890123";
    var wallet = new ethers.Wallet(privateKey);

    // Promises we are interested in
    var provider = ethers.providers.getDefaultProvider('ropsten');
    var balancePromise = provider.getBalance(wallet.address);
    var gasPricePromise = provider.getGasPrice();
    var transactionCountPromise = provider.getTransactionCount(wallet.address);

    var allPromises = Promise.all([
        gasPricePromise,
        balancePromise,
        transactionCountPromise
    ]);

    var sendPromise = allPromises.then(function (results) {
        // This function is ONLY called once ALL promises are fulfilled

        var gasPrice = results[0];
        var balance = results[1];
        var transactionCount = results[2];

        // Sending a transaction to an externally owned account (EOA) is 21000 gas)
        var txFeeInWei = gasPrice.mul(21000);

        // This will send the maximum amount (our balance minus the fee)
        var value = balance.sub(txFeeInWei);

        var transaction = {
            to: targetAddress,
            gasPrice: gasPrice,
            gasLimit: 21000,
            nonce: transactionCount,

            // The amount to send
            value: value,

            // Prevent replay attacks across networks
            chainId: provider.chainId,
        };

        var signedTransaction = wallet.sign(transaction);

        // By returning a Promise, the sendPromise will resolve once the
        // transaction is sent
        return provider.sendTransaction(signedTransaction);
    });

    var minedPromise = sendPromise.then(function (transaction) {
        // This will be called once the transaction is sent

        // This promise will be resolve once the transaction has been mined.
        return provider.waitForTransaction(transaction);
    });

    minedPromise.then(function (transaction) {
        console.log("The transaction was mined: Block " + transaction.blockNumber);
    });


    // Promises can be re-used for their value; it will not make the external
    // call again, and will provide the exact same result every time.
    balancePromise.then(function (balance) {
        // This *may* return before teh above allPromises, since it only
        // required one external call. Keep in mind asynchronous calls can
        // be called out of order.
        console.log(balance);
    });
}
module.exports = {
    fromPrivateKeyWallet,
    fromRandomWallet,
    fromEncryptedWallet,
    fromMnemonicWallet,
    fromBrainWallet,
    queryNetwork,
    transfer,
    sendingComplex,
    parsingTransactions,
    verifyingMessages,
    connectProviders,
    blockchainStatus,
    ethereumNameResolution,
    eventTypes,
    etherscan,
    HDNode,
    promisesAll
}
